﻿using System;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;

namespace Sttplay.Net.UDP
{
    public class RUDPEx_C
    {
        private GCHandle _gcHandle;
        private IntPtr _rudp;

        public event Action<byte[], bool, EndPoint> onRecv;
        public event Action<RCTP.Message, RCTP.SendState> onSend;
        public event Action<string> onErrorLog;

        private event Action<string> onErrorLogEvent;
        private static bool isSet;
        public RUDPEx_C()
        {
            if (!isSet)
            {
                IRCTP.SetRecvCallback(static_RecvCb);
                IRCTP.SetSendCallback(static_SendCb);
                IRCTP.SetErrLogCallback(static_ErrLogCb);
                isSet = true;
            }

            _gcHandle = GCHandle.Alloc(this);
            _rudp = IRCTP.RUDPEx_New(GCHandle.ToIntPtr(_gcHandle));
            onErrorLogEvent += ErrorLog;

        }

        public uint GetTimestamp()
        {
            return IRCTP.RUDPEx_GetTimestamp();
        }

        private void static_ErrLogCb(string error)
        {
            if (onErrorLogEvent != null)
                onErrorLogEvent(error);
        }

        private void ErrorLog(string error)
        {
            if (onErrorLog != null)
                onErrorLog(error);
        }

        private void static_RecvCb(byte[] buffer, EndPoint remote, bool reliable, IntPtr user)
        {
            GCHandle handle = GCHandle.FromIntPtr(user);
            RUDPEx_C owner = (RUDPEx_C)handle.Target;
            if (owner == null)
                return;
            if (owner.onRecv != null)
                owner.onRecv(buffer, reliable, remote);
        }

        private void static_SendCb(IntPtr message, RCTP.SendState state, IntPtr user)
        {
            GCHandle handle = GCHandle.FromIntPtr(user);
            RUDPEx_C owner = (RUDPEx_C)handle.Target;
            if (owner == null)
                return;
            if (owner.onSend != null)
            {
                RCTP.Message msg = new RCTP.Message();
                msg.id = (ulong)Marshal.ReadInt64(message);
                msg.state = (RCTP.PackageState)Marshal.ReadInt32(message, 8);
                msg.sendTs = (uint)Marshal.ReadInt32(message, 12);
                msg.waitTimeout = (uint)Marshal.ReadInt32(message, 16);
                IntPtr dataPtr = Marshal.ReadIntPtr(message, 24);
                int dataLen = (ushort)Marshal.ReadInt16(message, 32);
                msg.data = new byte[dataLen];
                Marshal.Copy(dataPtr, msg.data, 0, dataLen);
                msg.resendCount = (ushort)Marshal.ReadInt16(message, 98);
                int userdataOffset = 34;
                int port = BitConverter.ToUInt16(new byte[] { Marshal.ReadByte(message, userdataOffset + 3), Marshal.ReadByte(message, userdataOffset + 2) }, 0);
                string ip = string.Format("{0}.{1}.{2}.{3}", Marshal.ReadByte(message, userdataOffset + 4), Marshal.ReadByte(message, userdataOffset + 5), Marshal.ReadByte(message, userdataOffset + 6), Marshal.ReadByte(message, userdataOffset + 7));
                msg.param = new IPEndPoint(IPAddress.Parse(ip), port);
                owner.onSend(msg, state);
            }
        }

        public bool Initialize(IPAddress ip, int port)
        {
            return IRCTP.RUDPEx_Initialize(_rudp, ip.ToString(), port) == 0;
        }

        public bool JoinMulticastGroup(IPAddress group)
        {
            return IRCTP.RUDPEx_JoinMulticastGroup(_rudp, group.ToString()) == 0;
        }

        public void DropMulticastGroup()
        {
            IRCTP.RUDPEx_DropMulticastGroup(_rudp);
        }

        public IPEndPoint GetLocalEndPoint()
        {
            StringBuilder ip = new StringBuilder(16);
            int port = 0;
            if (IRCTP.RUDPEx_GetLocalEndPoint(_rudp, ip, ref port) != 0)
                return null;
            return new IPEndPoint(IPAddress.Parse(ip.ToString()), port);
        }

        public void Terminate()
        {
            DropMulticastGroup();
            IRCTP.RUDPEx_Terminate(_rudp);
        }

        public void Update(int timeout = 0)
        {
            IRCTP.RUDPEx_Update(_rudp, timeout);
        }

        public void SetOption(RCTP.Option option, uint value)
        {
            IRCTP.RUDPEx_SetOptionInt(_rudp, (int)option, value);
        }

        public void SetOption(RCTP.Option option, float value)
        {
            IRCTP.RUDPEx_SetOptionFloat(_rudp, (int)option, value);
        }

        public void SendTo(byte[] buffer, int index, int len, EndPoint remote, bool reliable)
        {
            IPEndPoint remoteEP = (IPEndPoint)remote;
            byte[] ipbs = Encoding.UTF8.GetBytes(remoteEP.Address.ToString() + " ");
            IRCTP.RUDPEx_Send(_rudp, buffer, index, len, ipbs, remoteEP.Port, reliable ? 1 : 0);
        }

        ~RUDPEx_C()
        {
            if (_rudp == IntPtr.Zero) return;
            IRCTP.RUDPEx_Delete(_rudp);
            _gcHandle.Free();
            _rudp = IntPtr.Zero;
            onErrorLogEvent -= ErrorLog;
        }
    }
}